# Joao Lencart e Silva: j.lencart@ua.pt
import netCDF4
from netCDF4 import Dataset
import os
import fnmatch
from odcutils._tools import cookbook as cbt
from odcutils._etc import _parts
import numpy as np
from datetime import datetime, timedelta

# Set default encoding
netCDF4.default_encoding = 'utf-8'

def search(wildcard, **kargs):
    """Main search function.
       Implemented keywords:
        All SEACOOS-v2.0; FISUA-v0.0 conventioned global attributes (single
        value for keyword) and the following variables:

        time: 2 value tuple with (begin_time, end_time) in Loch seconds.
              Use odcutils._etc._parts.dTime2Loch and multiply by 86400
              (seconds in day).
        lat : 2 value tuple with (begin_lat, end_lat) in decimal degrees
        lon : 2 value tuple with (begin_lon, end_lon) in decimal degrees
        z:    2 value tuple with (begin_z, end_z) in meters
        
        Attributes of variables can be used as keywords
        (e.g. short_name = 'salinity' to find files where salinty is a
        variable)
      
      Results from the keywords are intersected with a logical AND.
      If lists are passed, the result for that keyword will be the joinned
      results of each value (logical OR)."""
    # Start with results equal to the full universe of files
    results_aggr = set(wld2list(wildcard))
    # Loop all kargs
    for k in kargs.keys():
    # Find if k is list
        if _parts.isList(kargs[k]):
            results = set()
    # If is list then loop and get results for each value
    # Pass current aggregated results to narrow filelist
            for val in kargs[k]:
                r = subsearch(k, val, wildcard, filelist = list(results_aggr))
                map(results.add, r)
        else:
    # get results for a single value key
            r = subsearch(k, kargs[k], wildcard, filelist = list(results_aggr))
    # Convert results to set
            results = set(r)
    # Intersect the results for all kargs
        results_aggr = results_aggr.intersection(results)
    return list(results_aggr)    


def subsearch(key, val, wildcard, filelist = False):
    """Search for specific key in masked files""" 
    result = []
    # Check if key is a global attribute
    nc_attrs = getGlobalAttrs(wildcard, filelist = filelist)
    for nc in nc_attrs:
    # Try to find match for key. If key not present catch error
        try:
            if nc[key].find(val) > -1:
                result.append(nc['filename'])
        except KeyError:
            pass
    # Find generates an attribute error if val is not string
        except AttributeError:
            if nc[key] == val:
                result.append(nc['filename'])

    if not result:
    # Check if key is an attribute of a variable
        files = getVarAttrs(wildcard, filelist = filelist)
    # Loop files
        for file in files:
    # Loop variables inside each file
            for var in file['vars'].keys():
    # Try to find match for key. If key not present catch error
                try:
                    if file['vars'][var][key].find(val) > -1:
                        result.append(file['filename'])
                except KeyError:
                    pass
    # Find generates an attribute error if val is not string
                except AttributeError:
                    if nc[key] == val:
                        result.append(file['filename'])

    # If result still empty then try with variables
    if not result:
        result = searchvars(key, val, wildcard, filelist = filelist)
    return result


def searchvars(key, range, wildcard, filelist = False):
    """Search for key in variables"""
    # Check in wildcard or filelist
    if filelist is False:
    # List the files
        filelist = wld2list(wildcard)
    result = []
    # Loop files
    for fname in filelist:
    # Create Dataset object read only
        fdataset = Dataset(fname, 'r')
    # Try to get all data from keyword
        try:
            vals = fdataset.variables[key][:]
    # Go though the values and find if they are inside the range
            inrange = [contains(v, range) for v in vals]
            inrange = np.array(inrange)
    # Test if any of the values are in range
            if inrange.any():
               result.append(fname)
        except KeyError:
            pass
        fdataset.close()
    return result


def getGlobalAttrs(wildcard, filelist = False):
    """Go through the files set by wildcard and extract metadata"""
    attrs = []
    # Check in wildcard or filelist
    if filelist is False:
    # List the files
        filelist = wld2list(wildcard)
    # Go through files
    for file in filelist:
    # Create attribute dictionary
        attrdict = {}
    # Create netcdf object
        fdataset = Dataset(file)
    # Get global attribute names
        names = fdataset.ncattrs()
    # Get global attribute values and build dictionary
        for name in names:
            attrdict[name] = getattr(fdataset, name)
    # Add filename to dictionary
            attrdict['filename'] = file
    # Append to attributes list
        attrs.append(attrdict)
    # Close netcdf file
        fdataset.close()
    # Return attributes of every file
    return attrs

def getVarAttrs(wildcard, filelist = False):
    """Go through the files set by wildcard and extract variables 
    metadata"""
    varattrs = []
    # Check in wildcard or filelist
    if filelist is False:
    # List the files
        filelist = wld2list(wildcard)
    # Go through files
    for file in filelist:
    # Create a file dictionary
        filedict = {}
    # Create netcdf object
        fdataset = Dataset(file)
    # Create a var dictionary
        vardict = {}
    # Loop variables
        for var in fdataset.variables.keys():
    # Create var attribute dictionary
            varattrdict = {}
    # Get variables attribute names
            names = fdataset.variables[var].ncattrs()
    # Get variables attribute values and build dictionary
            for name in names:
                varattrdict[name] = getattr(fdataset.variables[var], name)
    # Add var attribute dictionary to var dictionary 
            vardict[var] = varattrdict
    # Add filename and var dictionary to file dict
        filedict['filename'] = file
        filedict['vars'] = vardict
    # Append to var attributes list
        varattrs.append(filedict)
    # Close netcdf file
        fdataset.close()
    # Return attributes of every file
    return varattrs


def wld2list(wildcard):
    """Converts wildcard expression to list of files"""
    adir = os.path.abspath(os.path.dirname(wildcard))
    apat = os.path.basename(wildcard)
    ls = fnmatch.filter(os.listdir(adir),apat)
    for i in range(0, len(ls)):
        ls[i] = os.path.join(adir, ls[i])
    return ls


def contains(value, range):
    """Test if range contains value  where range  is a tuple or a
    two value list, in open intervals.""" 
    if value >= np.min(range) and value <= np.max(range):
        return True
    else:
        return False
